#include <linux/module.h>
#include <linux/init.h>
#include "linux/fs.h"
#include "linux/slab.h"
#include "asm/uaccess.h"
#include <linux/sched.h>
#include "pipe.h"

static unsigned number_devices = 3;
static unsigned buffer_size;
static dev_t dev_num;
static struct pipe_device *devices;

static int open(struct inode *i, struct file *f)
{
    int retval = 0;

    struct pipe_device *dev = container_of(i->i_cdev, struct pipe_device, cdev);
    f->private_data = devices; /* for other function */

    if (!dev->start) /* allocate memory for buffer*/
    {
        dev->start = dev->rp = dev->wp = kmalloc(buffer_size, GFP_KERNEL);
        if (dev->start == NULL)
            return -ENOMEM;

        dev->end = dev->start + buffer_size - 1; /* FIXME */
    }

    return retval;
}

static int read(struct file *f, char __user *buf, size_t count, loff_t *pos)
{printk("pipe read() was called\n");
    struct pipe_device *dev = (struct pipe_device*)f->private_data;
    if (down_interruptible(&dev->sem))
        return -ERESTARTSYS;
    while (dev->start == dev->end) { /* no data for read */
        up(&dev->sem);
        /*if (f->f_flags & O_NONBLOCK) /* prohibited blocking current process */
          //  return -EAGAIN;
        if (wait_event_interruptible(dev->rq, (dev->rp != dev->wp))) /* add not exclusive current process to wait_queue_head_t */
            return -ERESTARTSYS;
        if (down_interruptible(&dev->sem))
            return -ERESTARTSYS;
    }
    if (dev->rp > dev->wp)
        count = min(count, (size_t)(dev->end - dev->rp));
    else
        count = min(count, (size_t)(dev->wp - dev->rp));
    if (copy_to_user(buf, dev->rp, count))
    {
        up(&dev->sem);
        return -EFAULT;
    }
    dev->rp += count;
    if (dev->rp == dev->end)
        dev->rp = dev->start;
    up(&dev->sem);
    wake_up_interruptible(&dev->wq);
    return count;
}

static int getFreeSpace(struct pipe_device *d)
{
    if (d->wp == d->rp)
        return d->end - d->start;
    return (d->rp + d->buffer_size - d->wp) % d->buffer_size;
}

static int checkFreeSpace(struct file *f, struct pipe_device *d)
{
    DEFINE_WAIT(wait); /* struct wait_queue_t wait */
    while (getFreeSpace(d) == 0) { /* buffer is full. Current proccess go sleep */
        up(&d->sem);
      /*  if (f->f_flags & O_NONBLOCK)
            return -EAGAIN;*/
        prepare_to_wait(&d->wq, &wait, TASK_INTERRUPTIBLE);
        schedule();
        finish_wait(&d->wq, &wait);
        if (down_interruptible(&d->sem))
            return -ERESTARTSYS;
    }
    return 0; /* Ok */
}

static int write(struct file *f, const char __user *buf, size_t count, loff_t *pos)
{
    int retval;
    struct pipe_device *dev = (struct pipe_device*)f->private_data;
    if (down_interruptible(&dev->sem))
        return -ERESTARTSYS;
    retval = checkFreeSpace(f, dev);
    if (retval)
        return retval;
    if (dev->wp > dev->rp)
        count = min(count, (size_t)(dev->end - dev->wp));
    else
        count = min(count, (size_t)(dev->rp - dev->wp) - 1);
    if (copy_from_user(buf, dev->wp, count))
    {
        up(&dev->sem);
        return -EFAULT;
    }
    dev->wp += count;
    if (dev->wp == dev->end)
        dev->wp = dev->start;
    up(&dev->sem);
    wake_up_interruptible(&dev->rq);
    return count;
}

static struct file_operations f_ops = {
    .open = open,
    .read = read,
    .write = write,
    //.release = release,
};

static void cdevSetup(struct cdev *c, short n)
{
    int d = dev_num + n;
    cdev_init(c, &f_ops);
    c->owner = THIS_MODULE;
    cdev_add(c, d, 1);
}

void clearPipeDevices()
{
    unregister_chrdev_region(dev_num, number_devices);
    if (devices)
        kfree(devices);
}

int initPipeDevices(dev_t d)
{
    int retval = 0;
    dev_num = d;

    retval = register_chrdev_region(d, number_devices, "pipe");
    if (retval < 0)
        return retval;

    devices = kmalloc(sizeof(struct pipe_device) * number_devices, GFP_KERNEL);
    if (devices == NULL)
    {
        retval = -ENOMEM;
        goto out;
    }
    memset(devices, 0, sizeof(struct pipe_device) * number_devices);

    int i = 0;
    for (; i < number_devices; i++) {
        init_waitqueue_head(&devices[i].rq);
        init_waitqueue_head(&devices[i].wq);
        sema_init(&devices[i].sem, 1);
        cdevSetup(&devices[i].cdev, i);
    }

    return retval;

out:
    clearPipeDevices();
    return retval;
}
